package edu.poly.cs9223.assginment1;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.autoscaling.AmazonAutoScalingClient;
import com.amazonaws.services.autoscaling.model.Activity;
import com.amazonaws.services.autoscaling.model.AutoScalingGroup;
import com.amazonaws.services.ec2.model.BlockDeviceMapping;
import com.amazonaws.services.autoscaling.model.CreateAutoScalingGroupRequest;
import com.amazonaws.services.autoscaling.model.CreateLaunchConfigurationRequest;
import com.amazonaws.services.autoscaling.model.DeleteAutoScalingGroupRequest;
import com.amazonaws.services.autoscaling.model.DeleteLaunchConfigurationRequest;
import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsRequest;
import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsResult;
import com.amazonaws.services.autoscaling.model.DescribeLaunchConfigurationsRequest;
import com.amazonaws.services.autoscaling.model.DescribeLaunchConfigurationsResult;
import com.amazonaws.services.autoscaling.model.DescribeScalingActivitiesRequest;
import com.amazonaws.services.autoscaling.model.DescribeScalingActivitiesResult;
import com.amazonaws.services.autoscaling.model.LaunchConfiguration;
import com.amazonaws.services.autoscaling.model.PutScalingPolicyRequest;
import com.amazonaws.services.autoscaling.model.PutScalingPolicyResult;
import com.amazonaws.services.autoscaling.model.ResumeProcessesRequest;
import com.amazonaws.services.autoscaling.model.SuspendProcessesRequest;
import com.amazonaws.services.autoscaling.model.UpdateAutoScalingGroupRequest;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Datapoint;
import com.amazonaws.services.cloudwatch.model.DescribeAlarmsResult;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.DisableAlarmActionsRequest;
import com.amazonaws.services.cloudwatch.model.EnableAlarmActionsRequest;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsResult;
import com.amazonaws.services.cloudwatch.model.MetricAlarm;
import com.amazonaws.services.cloudwatch.model.PutMetricAlarmRequest;
import com.amazonaws.services.cloudwatch.model.SetAlarmStateRequest;
import com.amazonaws.services.cloudwatch.model.StateValue;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.Address;
import com.amazonaws.services.ec2.model.AllocateAddressResult;
import com.amazonaws.services.ec2.model.AssociateAddressRequest;
import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
import com.amazonaws.services.ec2.model.CreateImageRequest;
import com.amazonaws.services.ec2.model.CreateImageResult;
import com.amazonaws.services.ec2.model.CreateKeyPairRequest;
import com.amazonaws.services.ec2.model.CreateKeyPairResult;
import com.amazonaws.services.ec2.model.CreateSecurityGroupRequest;
import com.amazonaws.services.ec2.model.CreateSecurityGroupResult;
import com.amazonaws.services.ec2.model.CreateSnapshotRequest;
import com.amazonaws.services.ec2.model.CreateSnapshotResult;
import com.amazonaws.services.ec2.model.DeleteKeyPairRequest;
import com.amazonaws.services.ec2.model.DeleteSecurityGroupRequest;
import com.amazonaws.services.ec2.model.DeleteSnapshotRequest;
import com.amazonaws.services.ec2.model.DeregisterImageRequest;
import com.amazonaws.services.ec2.model.DescribeAddressesResult;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeImagesResult;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.DescribeKeyPairsResult;
import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest;
import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult;
import com.amazonaws.services.ec2.model.DescribeSnapshotsRequest;
import com.amazonaws.services.ec2.model.DescribeSnapshotsResult;
import com.amazonaws.services.ec2.model.DescribeVolumesResult;
import com.amazonaws.services.ec2.model.EbsBlockDevice;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Image;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.IpPermission;
import com.amazonaws.services.ec2.model.KeyPair;
import com.amazonaws.services.ec2.model.KeyPairInfo;
import com.amazonaws.services.ec2.model.ReleaseAddressRequest;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.SecurityGroup;
import com.amazonaws.services.ec2.model.Snapshot;
import com.amazonaws.services.ec2.model.StartInstancesRequest;
import com.amazonaws.services.ec2.model.StopInstancesRequest;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import com.amazonaws.services.ec2.model.Volume;
import com.amazonaws.services.ec2.model.VolumeAttachment;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.CreateTopicRequest;
import com.amazonaws.services.sns.model.CreateTopicResult;

/**
 * TODO (1)监测idel的方式可能不对，（已经将Average改为Minimum，试试看） (2)卡在这步了....【Terminate
 * Instance】trying to terminate all ...DEBUG一下 {??} AWS Error Message: 1
 * validation error detected: Value '' at 'autoScalingGroupName' failed to
 * satisfy constraint: Member must have length greater than or equal to 1
 * 
 * 
 * (??)day2新创建的instance，没法加入autoscaling group==》导致没有cloud watch
 * metric上送===》没有onDemand能力(stopInstance无法触发) (3)auto scaling group max limit
 * ----no effect
 * 
 * @author Kevin Zhao
 */
public class Main {

	private static int COMM_RETRY_LIMIT = 1;// retry limit of commnication with
											// AWS
	private static int COMM_RETRY_WAITING_TIME = 1 * 1000;// 6s waiting

	static Logger logger = LoggerFactory.getLogger(Main.class);
	private static AmazonEC2 amazonEC2Client = null;
	private static AmazonAutoScalingClient amazonAutoScalingClient = null;
	private static AmazonCloudWatchClient amazonCloudWatchClient = null;
	private static AmazonSNSClient amazonSNSClient = null;
	private static Properties configurationProperties;
	private static AWSCredentials credentials = null;
	private static Thread cwCheckerThread = null;
	
	private static String DEFAULT_AMI_ID;
	private static String EC2_INSTANCE_TYPE;
	private static String AVAILABLE_ZONE;
	private static String SCALE_UP_POLICY_NAME;
	private static String SCALE_UP_POLICY_ARN = null;
	private static String SCALE_DOWN_POLICY_NAME;
	private static String SCALE_DOWN_POLICY_ARN = null;
	private static int COOL_DOWN;
	private static Integer IDLE_THRESHOLD;// CPU<10
	public static Integer IDLE_THRESHOLD_PERIOD;// CPU<5 for 2 min
	private static Double SCALE_UP_DONW_THRESHOLD;// CPU>80
	static Integer SCALE_UP_DOWN_THRESHOLD_PERIOD;// CPU>80 2min
	private static Integer EVALUATION_PERIOD;
	private static String METRIC_NAME;
	private static String METRIC_UNIT;
	private static String STATISTIC;
	private static String IDLE_ACTION_TRIGGERED_TOPIC_NAME;
	private static Integer MIN_INSTANCE_AMOUNT;// minimum of instance of user
	private static String TEST_USER_NAME;
	private static String DEMO_USER_NAME_A;
	private static String DEMO_USER_NAME_B;
	private static Integer USER_A_MAX_INSTANCE_CAPACITY;
	private static Integer USER_B_MAX_INSTANCE_CAPACITY;
	private static String DEBUG_MODE;

	// can have
	static {
		// 1. Specify AWS credentials
		InputStream credentialsAsStream = Thread.currentThread()
				.getContextClassLoader()
				.getResourceAsStream("AwsCredentials.properties");
		try {
			credentials = new PropertiesCredentials(credentialsAsStream);
		} catch (IOException e) {
			logger.error("Please put your AwsCredentials.properties file into the classpath and run the program again.");
		}
		// 2. To create and initialize an Amazon clients
		amazonEC2Client = new AmazonEC2Client(credentials);
		amazonAutoScalingClient = new AmazonAutoScalingClient(credentials);
		amazonCloudWatchClient = new AmazonCloudWatchClient(credentials);
		amazonSNSClient = new AmazonSNSClient(credentials);

		// 3. read configuration properties
		configurationProperties = new Properties();
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		InputStream stream = Thread.currentThread().getContextClassLoader()
				.getResourceAsStream("config.properties");
		try {
			configurationProperties.load(stream);
		} catch (IOException e) {
			logger.error("cannot find the configuration file 'config.properties'");
		}
		// 4. fill in
		DEFAULT_AMI_ID = configurationProperties.getProperty("DEFAULT_AMI_ID");
		EC2_INSTANCE_TYPE = configurationProperties
				.getProperty("EC2_INSTANCE_TYPE");
		AVAILABLE_ZONE = configurationProperties.getProperty("AVAILABLE_ZONE");
		SCALE_UP_POLICY_NAME = configurationProperties
				.getProperty("SCALE_UP_POLICY_NAME");
		SCALE_DOWN_POLICY_NAME = configurationProperties
				.getProperty("SCALE_DOWN_POLICY_NAME");
		COOL_DOWN = Integer.valueOf(configurationProperties
				.getProperty("COOL_DOWN"));
		IDLE_THRESHOLD = Integer.valueOf(configurationProperties
				.getProperty("IDLE_THRESHOLD"));
		IDLE_THRESHOLD_PERIOD = Integer.valueOf(configurationProperties
				.getProperty("IDLE_THRESHOLD_PERIOD"));
		SCALE_UP_DONW_THRESHOLD = Double.valueOf(configurationProperties
				.getProperty("SCALE_UP_DONW_THRESHOLD"));
		SCALE_UP_DOWN_THRESHOLD_PERIOD = Integer
				.valueOf(configurationProperties
						.getProperty("SCALE_UP_DOWN_THRESHOLD_PERIOD"));
		EVALUATION_PERIOD = Integer.valueOf(configurationProperties
				.getProperty("EVALUATION_PERIOD"));
		METRIC_NAME = configurationProperties.getProperty("METRIC_NAME");
		METRIC_UNIT = configurationProperties.getProperty("METRIC_UNIT");
		STATISTIC = configurationProperties.getProperty("STATISTIC");
		IDLE_ACTION_TRIGGERED_TOPIC_NAME = configurationProperties
				.getProperty("IDLE_ACTION_TRIGGERED_TOPIC_NAME");
		MIN_INSTANCE_AMOUNT = Integer.valueOf(configurationProperties
				.getProperty("MIN_INSTANCE_AMOUNT"));
		TEST_USER_NAME = configurationProperties.getProperty("TEST_USER_NAME");
		DEMO_USER_NAME_A = configurationProperties
				.getProperty("DEMO_USER_NAME_A");
		DEMO_USER_NAME_B = configurationProperties
				.getProperty("DEMO_USER_NAME_B");
		USER_A_MAX_INSTANCE_CAPACITY = Integer.valueOf(configurationProperties
				.getProperty("USER_A_MAX_INSTANCE_CAPACITY"));
		USER_B_MAX_INSTANCE_CAPACITY = Integer.valueOf(configurationProperties
				.getProperty("USER_B_MAX_INSTANCE_CAPACITY"));
		DEBUG_MODE = configurationProperties.getProperty("DEBUG_MODE");

	}

	public static User register(String name, int instanceAmount) {
		logger.info("[{}]【Register New User】{} is registering...", name, name);
		logger.debug("user {} registering", name);
		User user = new User();
		user.setName(name);
		user.setMaxInstanceAmount(instanceAmount);
		return user;
	}

	// private static String createEC2Instance(String keyName, String groupName,
	// int instanceAmount) {
	// RunInstancesRequest runInstancesRequest = new RunInstancesRequest();
	// runInstancesRequest.withImageId(EC2_IMAGE_ID)
	// .withInstanceType(EC2_INSTANCE_TYPE).withMinCount(1)
	// .withMaxCount(instanceAmount).withKeyName(keyName)
	// .withSecurityGroups(groupName);
	//
	// RunInstancesResult runInstancesResult = amazonEC2Client
	// .runInstances(runInstancesRequest);
	//
	// logger.debug("result for instances creation:{}",
	// runInstancesResult.toString());
	//
	// String reservationID = runInstancesResult.getReservation()
	// .getReservationId();
	// logger.debug("reservation id:{}", reservationID);
	//
	// logger.debug("getting instanc public dns...");
	// String instancePulicDNSName =
	// getInstancePublicDnsNameByRerservationID(reservationID);
	// while (instancePulicDNSName.equals("")) {
	// instancePulicDNSName =
	// getInstancePublicDnsNameByRerservationID(reservationID);
	// }
	//
	// return instancePulicDNSName;
	// }

	/**
	 * get instance DNS via reservationID
	 * 
	 * @param reservationID
	 * @return
	 */
	private static List<Instance> getInstanceIdsByRerservationID(
			String reservationID) {

		DescribeInstancesResult describeInstancesResult = amazonEC2Client
				.describeInstances();
		List<Reservation> reservations = describeInstancesResult
				.getReservations();
		Reservation desiredReservation = null;
		for (Reservation reservation : reservations) {
			if (reservation.getReservationId().equals(reservationID))
				desiredReservation = reservation;
		}
		return desiredReservation.getInstances();

	}

	private static String getInstancePublicDNSName(String instanceID) {
		DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
		describeInstancesRequest.withInstanceIds(instanceID);
		DescribeInstancesResult describeInstancesResult = amazonEC2Client
				.describeInstances(describeInstancesRequest);

		for (Reservation reservation : describeInstancesResult
				.getReservations()) {
			for (Instance instance : reservation.getInstances()) {
				if (instanceID.equals(instance.getInstanceId())) {
					return instance.getPublicDnsName();
				}
			}

		}
		return "";

	}

	public static void createKeyPair(User user) {

		DescribeKeyPairsResult result = amazonEC2Client.describeKeyPairs();
		List<KeyPairInfo> keys = result.getKeyPairs();
		for (KeyPairInfo key : keys) {
			if (key.getKeyName().equals(user.getKeyName())) {
				// delete the pre-existed one
				DeleteKeyPairRequest deleteKeyPairRequest = new DeleteKeyPairRequest();
				deleteKeyPairRequest.withKeyName(user.getKeyName());
				amazonEC2Client.deleteKeyPair(deleteKeyPairRequest);
			}
		}
		// create new one
		CreateKeyPairRequest createKeyPairRequest = new CreateKeyPairRequest();

		createKeyPairRequest.withKeyName(user.getKeyName());

		CreateKeyPairResult createKeyPairResult = amazonEC2Client
				.createKeyPair(createKeyPairRequest);

		KeyPair keyPair = new KeyPair();

		keyPair = createKeyPairResult.getKeyPair();

		String privateKey = keyPair.getKeyMaterial();
		// System.out.println("private key:" + privateKey);

		String priaveKeyPath = user.getKeyName() + ".pem";

		writeToFile(privateKey, priaveKeyPath);

		user.setPrivateKeyFingerPrint(keyPair.getKeyFingerprint());
		user.setKeyPath(priaveKeyPath);

	}

	public static void createSecurityGroup(User user) {

		// DescribeSecurityGroupsResult result = amazonEC2Client
		// .describeSecurityGroups();
		// List<SecurityGroup> groups = result.getSecurityGroups();
		// for (SecurityGroup group : groups) {
		// if (group.getGroupName().equals(groupName)) {
		// // delete the pre-existed one
		// DeleteSecurityGroupRequest deleteSecurityGroupRequest = new
		// DeleteSecurityGroupRequest();
		// deleteSecurityGroupRequest.withGroupName(groupName);
		// amazonEC2Client.deleteSecurityGroup(deleteSecurityGroupRequest);
		// }
		// }

		// create new one
		CreateSecurityGroupRequest createSecurityGroupRequest = new CreateSecurityGroupRequest();

		createSecurityGroupRequest.withGroupName(user.getSecurityGroupName())
				.withDescription("security group for " + user.getName());
		CreateSecurityGroupResult createSecurityGroupResult = amazonEC2Client
				.createSecurityGroup(createSecurityGroupRequest);

		// Before starting an Amazon EC2 instance, you next need to authorize
		// security group ingress and create a key pair to allow you to log into
		// your instance.

		// 4. To authorize security group ingress
		List<String> ipRanges = new ArrayList<String>();
		ipRanges.add("0.0.0.0/0");

		ArrayList<IpPermission> ipPermissions = new ArrayList<IpPermission>();
		IpPermission ipPermission = new IpPermission();
		ipPermission.setIpProtocol("tcp");
		ipPermission.setFromPort(new Integer(22));
		ipPermission.setToPort(new Integer(22));
		ipPermission.setIpRanges(ipRanges);
		ipPermissions.add(ipPermission);
		ipPermission.withIpRanges(ipRanges).withIpProtocol("tcp")
				.withFromPort(22).withToPort(22);

		try {
			// Authorize the ports to the used.
			AuthorizeSecurityGroupIngressRequest ingressRequest = new AuthorizeSecurityGroupIngressRequest(
					user.getSecurityGroupName(), ipPermissions);
			amazonEC2Client.authorizeSecurityGroupIngress(ingressRequest);
		} catch (AmazonServiceException ase) {
			// Ignore because this likely means the zone has already been
			// authorized.
			logger.error("【ERROR】{}", ase.getMessage());
		}

	}

	private static void runTest(User userA, User userB) throws Exception {

//		setupElasticity(userA);
//		setupOnDemand(userA);
//
//		logger.debug("input sth if you want to stop the instance");
//		readFromConsole();
//		stopInstances(userA);
//		// wait until console input
//		logger.debug("input sth when instance has been stopped and resume instance");
//		readFromConsole();
//		resumeInstances(userA);
//
//		logger.debug("input sth if you want to terminate instance");
//		readFromConsole();
//		terminateInstances(userA);
//
//		logger.debug("input sth if ami is ready and then create new instance from ami");
//		readFromConsole();
//		createNewInstance(userA);
//		resumeAutoScaling(userA);
//		resumeMonitoring(userA);
		
		
		List<String> instanceIds = new ArrayList<String>();
		DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
		Filter filters = new Filter();
		filters.setName("instance-state-name");
		Collection<String> values = Arrays.asList("stopped");
		filters.setValues(values);
		
		Filter groupFilters = new Filter();
		groupFilters.setName("instance.group-name");
		Collection<String> groupNamevalues = Arrays.asList("user-B_group");
		groupFilters.setValues(groupNamevalues);
		describeInstancesRequest.withFilters(filters,groupFilters);
		DescribeInstancesResult descInstanceResult = amazonEC2Client
				.describeInstances(describeInstancesRequest);
		descInstanceResult.toString();
	}

	private static void runDemo(User userA, User userB) throws Exception {
		Lock lock = new Lock();
		// logger.info("*************************************Day1***************************************");
		UserThread userA_Day1 = new UserThread(userA,lock, 1);
		UserThread userB_Day1 = new UserThread(userB,lock, 1);
		Thread userA_Day1Thread = new Thread(userA_Day1);
		Thread userB_Day1Thread = new Thread(userB_Day1);
		userA_Day1Thread.start();
		userB_Day1Thread.start();

		// wait until all day1 threads complete
		while(lock.getLockValue()<2)
		{
			Thread.sleep(1000*30);
		}
		//suspend CPU Utilization Monitoring
//		cwCheckerThread.sleep(100000000);

		logger.info("*************************************Day2***************************************");
		UserThread userA_Day2 = new UserThread(userA,lock, 2);
		UserThread userB_Day2 = new UserThread(userB,lock, 2);
		Thread userA_Day2Thread = new Thread(userA_Day2);
		Thread userB_Day2Thread = new Thread(userB_Day2);
		userA_Day2Thread.start();
		userB_Day2Thread.start();

	}

	private static void createLaunchConfiguration(User user) {
		DescribeLaunchConfigurationsRequest describeLaunchConfigurationsRequest = new DescribeLaunchConfigurationsRequest();
		describeLaunchConfigurationsRequest.withLaunchConfigurationNames(user
				.getLaunchConfigName());
		DescribeLaunchConfigurationsResult describeLaunchConfigurationResult = amazonAutoScalingClient
				.describeLaunchConfigurations(describeLaunchConfigurationsRequest);
		List<LaunchConfiguration> configs = describeLaunchConfigurationResult
				.getLaunchConfigurations();

		// launch config already exists
		if (configs.size() != 0)
			return;

		CreateLaunchConfigurationRequest createLaunchConfigurationRequest = new CreateLaunchConfigurationRequest();
		createLaunchConfigurationRequest
				.withLaunchConfigurationName(user.getLaunchConfigName())
				.withImageId(DEFAULT_AMI_ID)
				.withInstanceType(EC2_INSTANCE_TYPE)
				.withKeyName(user.getKeyName())
				.withSecurityGroups(user.getSecurityGroupName());
		amazonAutoScalingClient
				.createLaunchConfiguration(createLaunchConfigurationRequest);
	}

	private static void deleteLaunchConfiguration(User user) {
		logger.debug("delete launch configuration for [{}]", user.getName());
		// delete the pre-existed one
		if (amazonAutoScalingClient
				.describeLaunchConfigurations(
						new DescribeLaunchConfigurationsRequest()
								.withLaunchConfigurationNames(user
										.getLaunchConfigName()))
				.getLaunchConfigurations().size() != 0) {
			DeleteLaunchConfigurationRequest deleteLaunchConfigurationRequest = new DeleteLaunchConfigurationRequest();
			deleteLaunchConfigurationRequest.withLaunchConfigurationName(user
					.getLaunchConfigName());
			amazonAutoScalingClient
					.deleteLaunchConfiguration(deleteLaunchConfigurationRequest);
		}
	}

	private static void releaseElasticIP(User user) {
		for (String elasticIp : user.getElasticIP()) {
			logger.debug("releasing elastic ip [{}] for [{}]", elasticIp,
					user.getName());
			ReleaseAddressRequest releaseAddressRequest = new ReleaseAddressRequest();
			releaseAddressRequest.withPublicIp(elasticIp);
			amazonEC2Client.releaseAddress(releaseAddressRequest);
			logger.debug("successfully released elastic ip [{}] for [{}]",
					elasticIp, user.getName());
		}
	}

	private static void releaseAllElasticIP() {
		DescribeAddressesResult descElasticIpsResult = amazonEC2Client
				.describeAddresses();
		logger.debug("you have {} available elastic ip", descElasticIpsResult
				.getAddresses().size());
		for (Address elasticIp : descElasticIpsResult.getAddresses()) {
			logger.debug("releasing elastic ip [{}]", elasticIp.getPublicIp());
			ReleaseAddressRequest releaseAddressRequest = new ReleaseAddressRequest();
			releaseAddressRequest.withPublicIp(elasticIp.getPublicIp());
			amazonEC2Client.releaseAddress(releaseAddressRequest);
			logger.debug("successfully released elastic ip [{}]",
					elasticIp.getPublicIp());
		}
	}

	private static void createAutoScalingGroup(User user)
			throws InterruptedException {

		// delete the pre-existed one
		// deleteAutoScalingGroup(user);
		// create new ones
		CreateAutoScalingGroupRequest createAutoScalingGroupRequest = new CreateAutoScalingGroupRequest();
		createAutoScalingGroupRequest
				.withAutoScalingGroupName(user.getAutoScalingGroupName())
				.withLaunchConfigurationName(user.getLaunchConfigName())
				.withAvailabilityZones(AVAILABLE_ZONE)
				.withMinSize(MIN_INSTANCE_AMOUNT)
				.withMaxSize(user.getMaxInstanceAmount())
				.withTerminationPolicies("NewestInstance")
				.withDefaultCooldown(COOL_DOWN);
		// .withDesiredCapacity(MIN_INSTANCE_AMOUNT);
		amazonAutoScalingClient
				.createAutoScalingGroup(createAutoScalingGroupRequest);

		// update to use a new termination policy:[NewestInstance]
		// logger.debug(
		// "update auto scaling group [{}] to use new termination policy :[NewestInstance]",
		// user.getAutoScalingGroupName());
		// UpdateAutoScalingGroupRequest updateAutoScalingGroupRequest = new
		// UpdateAutoScalingGroupRequest();
		// updateAutoScalingGroupRequest.withAutoScalingGroupName(
		// user.getAutoScalingGroupName()).withTerminationPolicies(
		// "NewestInstance");
		// amazonAutoScalingClient
		// .updateAutoScalingGroup(updateAutoScalingGroupRequest);
	}

	private static List<AutoScalingGroup> getAllScalingGroup() {
		DescribeAutoScalingGroupsResult describeAutoScalingGroupResult = amazonAutoScalingClient
				.describeAutoScalingGroups();
		List<AutoScalingGroup> groups = describeAutoScalingGroupResult
				.getAutoScalingGroups();

		return groups;
	}

	private static void deleteAllAutoScalingGroups() {

		List<AutoScalingGroup> groups = getAllScalingGroup();

		for (AutoScalingGroup autoScalingGroup : groups) {
			String autoScalingGroupName = autoScalingGroup
					.getAutoScalingGroupName();
			DeleteAutoScalingGroupRequest deleteAutoScalingGroupRequest = new DeleteAutoScalingGroupRequest();
			deleteAutoScalingGroupRequest
					.withAutoScalingGroupName(autoScalingGroupName);
			amazonAutoScalingClient
					.deleteAutoScalingGroup(deleteAutoScalingGroupRequest);
			logger.debug("deleting auto scaling group  [{}] done",
					autoScalingGroupName);
		}

	}

	private static int getAutoScalingGroupAmount(User user) {
		DescribeAutoScalingGroupsRequest describeAutoScalingGroupsRequest = new DescribeAutoScalingGroupsRequest();
		describeAutoScalingGroupsRequest.withAutoScalingGroupNames(user
				.getAutoScalingGroupName());
		DescribeAutoScalingGroupsResult describeAutoScalingGroupResult = amazonAutoScalingClient
				.describeAutoScalingGroups(describeAutoScalingGroupsRequest);
		List<AutoScalingGroup> groups = describeAutoScalingGroupResult
				.getAutoScalingGroups();
		return groups.size();
	}

	private static void deleteAutoScalingGroup(User user)
			throws InterruptedException {
		logger.debug("delete auto scaling group for [{}]", user.getName());

		if (getAutoScalingGroupAmount(user) == 0)
			return;
		// waiting for auto scaling activities to complete
		waitForAutoScalingActivities(user);
		// delete the pre-existed one
		DeleteAutoScalingGroupRequest deleteAutoScalingGroupRequest = new DeleteAutoScalingGroupRequest();
		deleteAutoScalingGroupRequest.withAutoScalingGroupName(
				user.getAutoScalingGroupName()).withForceDelete(true);
		amazonAutoScalingClient
				.deleteAutoScalingGroup(deleteAutoScalingGroupRequest);
		logger.debug("deleting auto scaling group for [{}] done",
				user.getName());

		// wait until auto scaling group deletion complete
		boolean deletionDone = getAutoScalingGroupAmount(user) == 0;
		while (!deletionDone) {
			logger.debug(
					"wait for deletion of auto scaling group [{}] done for [{}]",
					user.getAutoScalingGroupName(), user.getName());
			Thread.sleep(COMM_RETRY_WAITING_TIME);
			deletionDone = getAutoScalingGroupAmount(user) == 0;
		}
		// Maintain internal state
		user.setAutoScalingGroupName("");
	}

	private static void waitForAutoScalingActivities(User user)
			throws InterruptedException {
		DescribeScalingActivitiesRequest describeScalingActivitiesRequest = new DescribeScalingActivitiesRequest();
		describeScalingActivitiesRequest.withAutoScalingGroupName(user
				.getAutoScalingGroupName());

		logger.debug(
				"waiting for all auto scaling activites of [{}] to accomplish",
				user.getName());
		while (true) {
			boolean allDone = true;
			
				System.out.print("*");
			Thread.sleep(COMM_RETRY_WAITING_TIME);

			DescribeScalingActivitiesResult scalingActivitiesResult = amazonAutoScalingClient
					.describeScalingActivities(describeScalingActivitiesRequest);
			for (Activity activity : scalingActivitiesResult.getActivities()) {
				if ("InProgress".equals(activity.getStatusCode())) {
					allDone = false;
				}
			}
			if (allDone)
				break;
		}

	}

	private static void createScalingActioins(User user) {
		// AutoScale Actions
		PutScalingPolicyRequest putScalingUpPolicyRequest = new PutScalingPolicyRequest();
		putScalingUpPolicyRequest.withPolicyName(SCALE_UP_POLICY_NAME)
				.withAutoScalingGroupName(user.getAutoScalingGroupName())
				.withScalingAdjustment(1)
				.withAdjustmentType("ChangeInCapacity").withCooldown(COOL_DOWN);

		PutScalingPolicyRequest putScalingDownPolicyRequest = new PutScalingPolicyRequest();
		putScalingDownPolicyRequest.withPolicyName(SCALE_DOWN_POLICY_NAME)
				.withAutoScalingGroupName(user.getAutoScalingGroupName())
				.withScalingAdjustment(-1)
				.withAdjustmentType("ChangeInCapacity").withCooldown(COOL_DOWN);

		PutScalingPolicyResult scalingupResult = amazonAutoScalingClient
				.putScalingPolicy(putScalingUpPolicyRequest);
		PutScalingPolicyResult scalingdownResult = amazonAutoScalingClient
				.putScalingPolicy(putScalingDownPolicyRequest);

		SCALE_UP_POLICY_ARN = scalingupResult.getPolicyARN();
		SCALE_DOWN_POLICY_ARN = scalingdownResult.getPolicyARN();

		// SNS Notification Actions
		// PutNotificationConfigurationRequest
		// putNotificationConfigurationRequest = new
		// PutNotificationConfigurationRequest();
		// putNotificationConfigurationRequest.withAutoScalingGroupName(user.getAutoScalingGroupName()).withNotificationTypes("autoscaling:TEST_NOTIFICATION").withTopicARN(user.getSnsTopicARN());
		// amazonAutoScalingClient.putNotificationConfiguration(putNotificationConfigurationRequest);

	}

	private static void createMetricAlarmAction(User user) {

		Dimension dimension = new Dimension();
		dimension.setName("AutoScalingGroupName");
		dimension.setValue(user.getAutoScalingGroupName());

		PutMetricAlarmRequest putMetricScaleUpAlarmRequest = new PutMetricAlarmRequest();
		putMetricScaleUpAlarmRequest
				.withAlarmName(user.getPutMetricScaleUpAlarmName())
				.withComparisonOperator("GreaterThanOrEqualToThreshold")
				.withUnit(METRIC_UNIT).withEvaluationPeriods(EVALUATION_PERIOD)
				.withMetricName(METRIC_NAME).withNamespace("AWS/EC2")
				.withPeriod(SCALE_UP_DOWN_THRESHOLD_PERIOD)
				.withStatistic(STATISTIC)
				.withThreshold(SCALE_UP_DONW_THRESHOLD)
				.withAlarmActions(SCALE_UP_POLICY_ARN)
				.withDimensions(dimension).withActionsEnabled(true);

		PutMetricAlarmRequest putMetricScaleDownAlarmRequest = new PutMetricAlarmRequest();
		putMetricScaleDownAlarmRequest
				.withAlarmName(user.getPutMetricScaleDownAlarmName())
				.withComparisonOperator("LessThanOrEqualToThreshold")
				.withUnit(METRIC_UNIT).withEvaluationPeriods(EVALUATION_PERIOD)
				.withMetricName(METRIC_NAME).withNamespace("AWS/EC2")
				.withPeriod(SCALE_UP_DOWN_THRESHOLD_PERIOD)
				.withStatistic(STATISTIC)
				.withThreshold(SCALE_UP_DONW_THRESHOLD)
				.withAlarmActions(SCALE_DOWN_POLICY_ARN)
				.withDimensions(dimension).withActionsEnabled(true);

		amazonCloudWatchClient.putMetricAlarm(putMetricScaleUpAlarmRequest);
		amazonCloudWatchClient.putMetricAlarm(putMetricScaleDownAlarmRequest);

		// set the alarm value from insufficientData to OK
		logger.debug(
				"initializing the cloud watch alarms by setting their states to OK for [{}]",
				user.getName());
		setCloudWatchAlarmState(user.getPutMetricScaleUpAlarmName(),
				StateValue.OK);
		setCloudWatchAlarmState(user.getPutMetricScaleDownAlarmName(),
				StateValue.OK);

	}

	private static void setCloudWatchAlarmState(String alarmName,
			StateValue stateValue) {
		logger.debug("set the alarm[{}] to [{}] state", alarmName,
				stateValue.name());
		SetAlarmStateRequest setAlarmStateRequest = new SetAlarmStateRequest();
		setAlarmStateRequest
				.withStateValue(stateValue)
				.withAlarmName(alarmName)
				.withStateReason(
						"manually set the alarm state to " + stateValue.name()
								+ " by the program");
		amazonCloudWatchClient.setAlarmState(setAlarmStateRequest);
	}

	private static List<com.amazonaws.services.autoscaling.model.Instance> getAvailableInstances(
			User user) throws InterruptedException {

		DescribeAutoScalingGroupsRequest describeAutoScalingGroupsRequest = new DescribeAutoScalingGroupsRequest();
		describeAutoScalingGroupsRequest.withAutoScalingGroupNames(user
				.getAutoScalingGroupName());

		DescribeAutoScalingGroupsResult result = amazonAutoScalingClient
				.describeAutoScalingGroups(describeAutoScalingGroupsRequest);
		int retry = 0;
		logger.debug("[try:{}/{}]getting auto scalingGroups for [{}]...",
				retry, COMM_RETRY_LIMIT, user.getName());
		while (result.getAutoScalingGroups().size() == 0) {
			if (retry++ == COMM_RETRY_LIMIT) {
				logger.debug("can not get auto scalingGroups for [{}]",
						user.getName());
				return new ArrayList<com.amazonaws.services.autoscaling.model.Instance>();
			}
			
				System.out.print("*");
			Thread.sleep(COMM_RETRY_WAITING_TIME);
			result = amazonAutoScalingClient
					.describeAutoScalingGroups(describeAutoScalingGroupsRequest);
		}
		AutoScalingGroup group = result.getAutoScalingGroups().get(0);

		return group.getInstances();

	}

	private static List<String> getAvailableInstanceIds(User user)
			throws InterruptedException {
		List<com.amazonaws.services.autoscaling.model.Instance> instances = getAvailableInstances(user);
		List<String> result = new ArrayList<String>();
		for (com.amazonaws.services.autoscaling.model.Instance instance : instances) {
			result.add(instance.getInstanceId());
		}
		return result;

	}

	private static void createUserInfo(User user) throws InterruptedException {
		// set up instance dns for ssh
		logger.debug("getting available instances for [{}]...", user.getName());
		logger.info(
				"[{}]【Waiting for Instance】[{}] is waiting for instance to be in running state",
				user.getName(), user.getName());
		boolean hasRunningInstances = hasRunningInstances(user);
		while (!hasRunningInstances) {
			
				System.out.print("*");
			Thread.sleep(COMM_RETRY_WAITING_TIME);
			hasRunningInstances = hasRunningInstances(user);
		}

		List<String> instancesIds = getAllInstanceIdsWithStatusFilterForUser(user, "running");

		// set up elastic ip
		logger.debug("setting elastic ip for [{}]...", user.getName());
		for (String instanceId : instancesIds) {
			// add instance id
			logger.debug("add instance id[{}] for [{}]",
					instanceId, user.getName());
			user.addInstanceId(instanceId);
			// get elastic id
			logger.debug("get elastic ip for [{}]", instanceId,
					user.getName());
			AllocateAddressResult allocResult = amazonEC2Client
					.allocateAddress();
			String elasticIP = allocResult.getPublicIp();
			// associate instance id with elastic ip
			logger.info(
					"[{}]【Get Elastic IP】attach  elastic ip [{}] to instance[{}] for[{}]",
					user.getName(), elasticIP, instanceId,
					user.getName());
			AssociateAddressRequest associateAddressRequest = new AssociateAddressRequest();
			associateAddressRequest.withInstanceId(instanceId)
					.withPublicIp(elasticIP);
			amazonEC2Client.associateAddress(associateAddressRequest);
			// add elastic ip
			logger.debug("add elasticIP [{}] for [{}]", elasticIP,
					user.getName());
			user.addElasticIP(elasticIP);
		}
	}

	public static void logout(User user) throws Exception {
		logger.info("[{}]【Log Out】", user.getName());

		// terminateInstance
		terminateInstances(user);
		// suspend auto scaling and monitoring
		// logger.debug("suspend auto scaling &CloudWatch alarm for [{}]",
		// user.getName());
		
	}

	private static void createSNSTopic(User user) {
		CreateTopicRequest createTopicRequest = new CreateTopicRequest();
		createTopicRequest.withName(IDLE_ACTION_TRIGGERED_TOPIC_NAME);
		CreateTopicResult createTopicResult = amazonSNSClient
				.createTopic(createTopicRequest);
		user.setSnsTopicARN(createTopicResult.getTopicArn());
	}

	private static void createSNSTopicListener(User user) throws Exception {
		// create listener
		TopicListener snsTopicListener = new TopicListener(
				credentials.getAWSAccessKeyId(), credentials.getAWSSecretKey(),
				user.getSnsTopicARN());
		user.setSnsTopicListener(snsTopicListener);
		// fire up the listener
		snsTopicListener.run();
	}

	public static void setupOnDemand(User user) throws Exception {
		// setup SNS topics and subscriptioins
		// createSNSTopic(user);
		// setup SNS topic subsriber listener
		// createSNSTopicListener(user);
		//
		// scheduled start(manually)

		// detect idle(new thread check every 30seconds)
		CloudWatchChecker cwChecker = new CloudWatchChecker(credentials, user);
		cwCheckerThread = new Thread(cwChecker);
		cwCheckerThread.start();
		// scheduled shutdown(manually)
	}

	public static void setupElasticity(User user) throws Exception {
		// setup auto scale launch config
		createLaunchConfiguration(user);
		// setup auto scale group
		createAutoScalingGroup(user);
		// setup auto scale policy
		createScalingActioins(user);
		// setup cloud watch triggers
		createMetricAlarmAction(user);

		// get instance id and public DNS
		createUserInfo(user);
	}

	public static String readFromConsole() {

		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);

		try {
			String line = null;
			while (!(line = br.readLine()).equals("")) {
				return line;
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void main(String[] args) throws Exception {
		System.out.println("Please insert your Amazon secretKey and accessKey in the AwsCredentials.properties before running the program");
		System.out.println("you can get your AWS Credentials from http://aws.amazon.com/security-credentials");
		System.out.println("==========================Cloud Computing Assignment-1 by Yuan Zhao(0488541)===========================================");
		System.out.println("[1]:RUN Assignment DEMO");
		System.out.println("[2]:clean up(delete all the things related to this demo on AWS.Take it easy,your other stuffs will be left intact  :))");
		System.out.println("[3]:run test");
		System.out.println("[4]:clean up all(!!!!This option will delete all the stuffs on your account!!!!)");
		System.out.println("=======================================================================================================================");
		System.out.println("please enther the option number here===>:");

		String option = readFromConsole();
		if ("1".equals(option)) {
			//clean up first
			User userA = new User();
			User userB = new User();
			userA.setName(DEMO_USER_NAME_A);
			userB.setName(DEMO_USER_NAME_B);
			cleanUp(userA);
			cleanUp(userB);
			//demo begin
			logger.info("*************************************New User registeration***************************************");
			if (!DEBUG_MODE.equalsIgnoreCase("true")) {
				userA = register(DEMO_USER_NAME_A, USER_A_MAX_INSTANCE_CAPACITY);
				userB = register(DEMO_USER_NAME_B, USER_B_MAX_INSTANCE_CAPACITY);
			} else {
				userA = register(DEMO_USER_NAME_A + "_" + UUID.randomUUID(),
						USER_A_MAX_INSTANCE_CAPACITY);
				userB = register(DEMO_USER_NAME_B + "_" + UUID.randomUUID(),
						USER_B_MAX_INSTANCE_CAPACITY);
			}

			runDemo(userA, userB);
		} else if ("2".equals(option)) {
			User userA = new User();
			User userB = new User();
			userA.setName("userA");
			userB.setName("userB");
			cleanUp(userA);
			cleanUp(userB);
		} else if ("3".equals(option)) {
			User userA = Main.register(TEST_USER_NAME, 2);
			runTest(userA, null);
		} else if ("4".equals(option)) {
			cleanUpAll();
		} else {
			logger.error("{} is not a valid option number", option);
		}

	}

	private static List<String> getAllInstanceIdsWithStatusFilterForUser(User user,
			String... statuses) {
		List<String> instanceIds = new ArrayList<String>();
		DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
		Filter filters = new Filter();
		filters.setName("instance-state-name");
		Collection<String> values = Arrays.asList(statuses);
		filters.setValues(values);
		
		Filter groupFilters = new Filter();
		groupFilters.setName("instance.group-name");
		Collection<String> groupNamevalues = Arrays.asList(user.getSecurityGroupName());
		groupFilters.setValues(groupNamevalues);
		describeInstancesRequest.withFilters(filters,groupFilters);
		DescribeInstancesResult descInstanceResult = amazonEC2Client
				.describeInstances(describeInstancesRequest);

		List<Reservation> reservations = descInstanceResult.getReservations();

		for (Reservation reservation : reservations) {
			List<Instance> instances = reservation.getInstances();
			for (Instance instance : instances) {
				instanceIds.add(instance.getInstanceId());
			}
		}
		return instanceIds;
	}
	private static List<String> getAllInstanceIdsWithStatusFilter(
			String... statuses) {
		List<String> instanceIds = new ArrayList<String>();
		DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
		Filter filters = new Filter();
		filters.setName("instance-state-name");
		Collection<String> values = Arrays.asList(statuses);
		filters.setValues(values);
		describeInstancesRequest.withFilters(filters);
		DescribeInstancesResult descInstanceResult = amazonEC2Client
				.describeInstances(describeInstancesRequest);

		List<Reservation> reservations = descInstanceResult.getReservations();

		for (Reservation reservation : reservations) {
			List<Instance> instances = reservation.getInstances();
			for (Instance instance : instances) {
				instanceIds.add(instance.getInstanceId());
			}
		}
		return instanceIds;
	}

	private static void deletAutoScalingGroup(String groupName) {
		// pause all ongoing auto scaling activities (first update,then
		// resume-process,and then suspend-process)
		logger.debug("pause all ongoing auto scaling activities");

		// update
		UpdateAutoScalingGroupRequest updateAutoScalingGroupARequest = new UpdateAutoScalingGroupRequest();
		updateAutoScalingGroupARequest.withAutoScalingGroupName(groupName)
				.withMinSize(0).withMaxSize(0);
		amazonAutoScalingClient
				.updateAutoScalingGroup(updateAutoScalingGroupARequest);
		// Data VO
		User user = new User();
		user.setAutoScalingGroupName(groupName);
		user.setAutoScalingGroupName(groupName);
		// resumeAutoScaling
		resumeAutoScaling(user);
		// suspendAutoScaling
		suspendAutoScaling(user);
		// delete auto scaling group
		logger.debug("delete auto scaling group [{}]", groupName);
		DeleteAutoScalingGroupRequest deleteAutoScalingGroupRequest = new DeleteAutoScalingGroupRequest();
		deleteAutoScalingGroupRequest.withForceDelete(true)
				.withAutoScalingGroupName(groupName);
		amazonAutoScalingClient
				.deleteAutoScalingGroup(deleteAutoScalingGroupRequest);
		logger.debug("deleting auto scaling group  [{}] done", groupName);
	}

	private static void cleanUpAll() throws InterruptedException {
		// realease elasticIP
		releaseAllElasticIP();

		//TODO delete keys
		
		//
		List<AutoScalingGroup> groups = getAllScalingGroup();
		logger.info("cleanning up start!");

		// pause all ongoing auto scaling activities (first update,then
		// resume-process,and then suspend-process)
		logger.info("pause all ongoing auto scaling activities");

		// update
		for (AutoScalingGroup group : groups) {
			String groupName = group.getAutoScalingGroupName();
			UpdateAutoScalingGroupRequest updateAutoScalingGroupARequest = new UpdateAutoScalingGroupRequest();
			updateAutoScalingGroupARequest.withAutoScalingGroupName(groupName)
					.withMinSize(0).withMaxSize(0);
			amazonAutoScalingClient
					.updateAutoScalingGroup(updateAutoScalingGroupARequest);
		}

		Thread.sleep(COMM_RETRY_WAITING_TIME * 5);
		// resumeAutoScaling
		for (AutoScalingGroup group : groups) {
			User user = new User();
			user.setAutoScalingGroupName(group.getAutoScalingGroupName());
			resumeAutoScaling(user);
		}
		Thread.sleep(COMM_RETRY_WAITING_TIME * 5);
		// suspendAutoScaling
		for (AutoScalingGroup group : groups) {
			User user = new User();
			user.setAutoScalingGroupName(group.getAutoScalingGroupName());
			suspendAutoScaling(user);
		}

		// delete auto scaling group
		for (AutoScalingGroup group : groups) {
			deletAutoScalingGroup(group.getAutoScalingGroupName());
		}

		// suspendMonitoring
		DescribeAlarmsResult alarmsResult = amazonCloudWatchClient
				.describeAlarms();
		List<MetricAlarm> alarms = alarmsResult.getMetricAlarms();
		if (alarms.size() > 0) {
			List<String> alarmNames = new ArrayList<String>();
			for (MetricAlarm alarm : alarms) {
				alarmNames.add(alarm.getAlarmName());
			}
			DisableAlarmActionsRequest disableAlarmActionsRequest = new DisableAlarmActionsRequest();
			disableAlarmActionsRequest.withAlarmNames(alarmNames);
			amazonCloudWatchClient
					.disableAlarmActions(disableAlarmActionsRequest);
		}

		// termiate all instances
		logger.info("termiate all instances");
		List<String> instanceIds = getAllInstanceIdsWithStatusFilter("running",
				"stopping", "stopped", "pending");
		if (instanceIds.size() > 0) {
			TerminateInstancesRequest terminateInstancesARequest = new TerminateInstancesRequest();
			terminateInstancesARequest.withInstanceIds(instanceIds);
			amazonEC2Client.terminateInstances(terminateInstancesARequest);
			// waiting for terminating instances...it will usually take 30
			// seconds
			// or so...
			int notTerminated = 100;
			logger.debug("waiting for terminating instances...it will usually take 30 seconds or so...");
			while (notTerminated != 0) {
				
					System.out.print("*");
				Thread.sleep(COMM_RETRY_WAITING_TIME);// it will
																		// usually
																		// take
																		// 30s
																		// more
																		// to
																		// terminate
																		// all
																		// instances

				notTerminated = getAllInstanceIdsWithStatusFilter("pending",
						"running", "shutting-down", " stopping", "stopped")
						.size();
			}
		}

		logger.info("delete launch configuration");
		// delete launch configuration
		DescribeLaunchConfigurationsResult lcResult = amazonAutoScalingClient
				.describeLaunchConfigurations();
		List<LaunchConfiguration> lcs = lcResult.getLaunchConfigurations();
		for (LaunchConfiguration lc : lcs) {
			DeleteLaunchConfigurationRequest deleteLaunchConfigurationRequest = new DeleteLaunchConfigurationRequest();
			String launchConfigurationName = lc.getLaunchConfigurationName();
			deleteLaunchConfigurationRequest
					.withLaunchConfigurationName(launchConfigurationName);
			amazonAutoScalingClient
					.deleteLaunchConfiguration(deleteLaunchConfigurationRequest);

		}

		logger.info("delete all security groups");
		DescribeSecurityGroupsResult descGroupsResult = amazonEC2Client
				.describeSecurityGroups();

		for (SecurityGroup group : descGroupsResult.getSecurityGroups()) {
			if (group.getGroupName().equals("default"))
				continue;
			DeleteSecurityGroupRequest deleteSecurityGroupRequest = new DeleteSecurityGroupRequest();
			deleteSecurityGroupRequest.withGroupName(group.getGroupName());
			amazonEC2Client.deleteSecurityGroup(deleteSecurityGroupRequest);
		}
		logger.info("delete ami");
		// delete ami
		DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest();
		describeImagesRequest.withOwners("self");
		DescribeImagesResult imagesResult = amazonEC2Client
				.describeImages(describeImagesRequest);
		List<Image> images = imagesResult.getImages();
		for (Image image : images) {
			DeregisterImageRequest deregisterImageRequest = new DeregisterImageRequest();
			String imageId = image.getImageId();
			deregisterImageRequest.withImageId(imageId);
			amazonEC2Client.deregisterImage(deregisterImageRequest);
		}

		logger.info("deleteAllSnapshots");
		// deleteAllSnapshots
		deleteAllSnapshots();

		logger.info("clean up all done!");
	}

	private static void cleanUp(User user) throws Exception {
		logger.info("=======================================================================");
		logger.info("【Cleanning up】cleaning up the environment...");
		// release all elastic ip
		releaseElasticIP(user);

		// suspend cloud watch alarms

		suspendMonitoring(user);

		// logger.debug("【Cleanning up】pause all ongoing auto scaling activities");
		// // pause all ongoing auto scaling activities
		// // See
		// //
		// here:http://www.mspconsultants.co.uk/index.php/blog/85-aws/89-auto-scale-ghost-instances-aws-ec2
		// if (amazonAutoScalingClient
		// .describeAutoScalingGroups(
		// new DescribeAutoScalingGroupsRequest()
		// .withAutoScalingGroupNames(user
		// .getAutoScalingGroupName()))
		// .getAutoScalingGroups().size() != 0) {
		// logger.debug("update auto scaling group policy to min:0 max:0");
		// UpdateAutoScalingGroupRequest updateAutoScalingGroupARequest = new
		// UpdateAutoScalingGroupRequest();
		// updateAutoScalingGroupARequest
		// .withAutoScalingGroupName(user.getAutoScalingGroupName())
		// .withMinSize(0).withMaxSize(0);
		// amazonAutoScalingClient
		// .updateAutoScalingGroup(updateAutoScalingGroupARequest);
		// }
		//
		// // resume auto scaling
		// resumeAutoScaling(user);
		// // suspend auto scaling
		// suspendAutoScaling(user);

		// terminate instances
		logger.debug("terminating instances for [{}] and [{}]", user.getName(),
				user.getName());
		 List<String> instanceIdsToTerminate = getAllInstanceIdsWithStatusFilterForUser(user, "running",
				"stopping", "stopped", "pending");
		if (instanceIdsToTerminate.size() != 0) {
			TerminateInstancesRequest terminateInstancesARequest = new TerminateInstancesRequest();
			terminateInstancesARequest.withInstanceIds(instanceIdsToTerminate);
			amazonEC2Client.terminateInstances(terminateInstancesARequest);
		}

		// waiting for terminating instances...it will usually take 30 seconds
		// or so...
		int notTerminated = 100;
		logger.debug("waiting for terminating instances...it will usually take 30 seconds or so...");
		while (notTerminated != 0) {
			
				System.out.print("*");
			Thread.sleep(COMM_RETRY_WAITING_TIME);// it will
																	// usually
																	// take
																	// 30s more
																	// to
																	// terminate
																	// all
																	// instances

			notTerminated = checkInstancesWithSpecificStatus(user, "pending",
					"running", "shutting-down", " stopping", "stopped");
		}

		// Terminate Autoscaling group
		logger.debug("deleting auto scaling group for", user.getName());
		deleteAutoScalingGroup(user);
		// delete launch configurations
		deleteLaunchConfiguration(user);
		// TODO delete cloud watch alarms

		// delete security group
		DescribeSecurityGroupsResult descrGroupResult = amazonEC2Client
				.describeSecurityGroups();
		boolean groupExisted = false;
		for (SecurityGroup group : descrGroupResult.getSecurityGroups()) {
			if (group.getGroupName().equals(user.getSecurityGroupName()))
				groupExisted = true;
		}
		if (groupExisted) {
			logger.debug("deleting security group for {}", user.getName());
			DeleteSecurityGroupRequest deleteSecurityGroupRequest = new DeleteSecurityGroupRequest();
			deleteSecurityGroupRequest.withGroupName(user
					.getSecurityGroupName());
			amazonEC2Client.deleteSecurityGroup(deleteSecurityGroupRequest);
		}

		// delete keys

		// delete EBS volumns

		// deregister and delete ami image in s3
		deleteAMI(user);
		// delete all snapshots
		deleteSnapshots(user);
		// DeregisterImageRequest

		// delete s3 bucket

		logger.info("[{}]【Cleanning up】Done!", user.getName());
		logger.info("=======================================================================");
	}

	private static void deleteSnapshots(User user) {
		if (user.getSnapshotIds().size() == 0
				|| amazonEC2Client
						.describeSnapshots(
								new DescribeSnapshotsRequest().withOwnerIds(
										"self").withSnapshotIds(
										user.getSnapshotIds())).getSnapshots()
						.size() == 0)
			return;
		logger.debug("deleting snapshots:{} for [{}]", user.getSnapshotIds(),
				user.getName());
		for (String snapshotId : user.getSnapshotIds()) {
			DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest();
			deleteSnapshotRequest.withSnapshotId(snapshotId);
			amazonEC2Client.deleteSnapshot(deleteSnapshotRequest);
		}

		user.getSnapshotIds().clear();
	}

	private static void deleteAllSnapshots() {
		logger.info("looking for snapshots");
		DescribeSnapshotsRequest describeSnapshotsRequest = new DescribeSnapshotsRequest();
		describeSnapshotsRequest.withOwnerIds("self");
		DescribeSnapshotsResult descSnapshotsResult = amazonEC2Client
				.describeSnapshots(describeSnapshotsRequest);
		List<Snapshot> snapshots = descSnapshotsResult.getSnapshots();
		if (snapshots.size() == 0)
			return;
		for (Snapshot snapshot : snapshots) {
			logger.info("delete snapshot:{}", snapshot.getSnapshotId());
			DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest();
			deleteSnapshotRequest.withSnapshotId(snapshot.getSnapshotId());
			amazonEC2Client.deleteSnapshot(deleteSnapshotRequest);
		}
	}

	private static void deleteAMI(User user) {
		List<String> amiIds = user.getAmiIds();
		if (user.getAmiIds().size() == 0
				|| amazonEC2Client
						.describeImages(
								new DescribeImagesRequest()
										.withImageIds(amiIds)
										.withOwners("self")).getImages().size() == 0)
			return;

		for (String amiId : amiIds) {

			logger.debug("deleting AMI:{} for [{}]", user.getAmiIds(),
					user.getName());
			DeregisterImageRequest deregisterImageRequest = new DeregisterImageRequest();
			deregisterImageRequest.withImageId(amiId);
			amazonEC2Client.deregisterImage(deregisterImageRequest);
		}
	}

	private static void writeToFile(String text, String path) {
		FileWriter fileWriter = null;
		try {
			String content = text;
			File newTextFile = new File(path);
			fileWriter = new FileWriter(newTextFile);
			fileWriter.write(content);
			fileWriter.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			try {
				fileWriter.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
	}

	private static boolean hasRunningInstances(User user)
			throws InterruptedException {
		int numberOfRunningInstances = checkInstancesWithSpecificStatus(user,
				"running");
		if (numberOfRunningInstances == 0) {
			logger.debug("[{}] has no running instances for now",
					user.getName());
			return false;
		}
		return true;

	}

	/**
	 * Check CloudWatch CPU Utilization Metric to determine whether to shut down
	 * instance or not
	 * 
	 * @param cwResult
	 * @throws InterruptedException
	 * @throws Exception
	 */
	public static void onDemandCheck(User user,
			GetMetricStatisticsResult cwResult) throws InterruptedException {

		if (!hasRunningInstances(user))
			return;

		List<Datapoint> dataPoints = cwResult.getDatapoints();
		for (Datapoint dataPoint : dataPoints) {
			boolean isIdle = dataPoint.getAverage() <= IDLE_THRESHOLD;
			logger.info(
					"[{}]【CPU Utilization:{}%】【Idle:{}】CloudWatch Data for [{}]:[{}]",
					user.getName(), dataPoint.getAverage(), isIdle,
					user.getName(), dataPoint.toString());
			if (isIdle) {
				logger.info(
						"[{}] is idle for {} seconds,stop the instances for him/her...",
						user.getName(), IDLE_THRESHOLD_PERIOD);
				try {
					stopInstances(user);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void resumeInstances(User user) throws Exception {

		//restart CPUUtilization monitoring
//		cwCheckerThread.start();
		logger.info("[{}]【Resume Instance】", user.getName());
		// start instance that is in stopped status
		StartInstancesRequest startInstancesRequest = new StartInstancesRequest();
		startInstancesRequest.withInstanceIds(getAllInstanceIdsWithStatusFilterForUser(user,"running","stopped","stopping"));
		amazonEC2Client.startInstances(startInstancesRequest);
		// resume autoscaling & monitoring
		resumeAutoScaling(user);
		resumeMonitoring(user);

	}

	public static void createNewInstance(User user) throws InterruptedException {
		int index = 0;
		for (String amiId : user.getAmiIds()) {
			// set info for snapshot volumn that will be attached to the newly
			// created instance
			// more info
			// here:http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
			EbsBlockDevice ebsBlockDevice = new EbsBlockDevice();
			ebsBlockDevice.withSnapshotId(user.getSnapshotIds().get(index))
					.withDeleteOnTermination(true);

			BlockDeviceMapping blockDeviceMapping = new BlockDeviceMapping();
			blockDeviceMapping.withDeviceName("/dev/sdf1").withEbs(
					ebsBlockDevice);

			// wait until ami is in "available state"
			DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest();
			Filter stateFilter = new Filter();
			stateFilter.withName("state").withValues("available");
			Filter idFilter = new Filter();
			idFilter.withName("image-id").withValues(amiId);

			DescribeImagesRequest descImgRequest = describeImagesRequest
					.withFilters(stateFilter, idFilter);
			DescribeImagesResult descImgResult = amazonEC2Client
					.describeImages(describeImagesRequest);
			boolean amiAvailabe = descImgResult.getImages().size() > 0;
			logger.info(
					"[{}]【Wait for AMI】wait until ami [{}] to be in 'available state'",
					user.getName(), amiId);
			while (!amiAvailabe) {
				System.out.print("*");
				Thread.sleep(COMM_RETRY_WAITING_TIME);
				descImgResult = amazonEC2Client
						.describeImages(describeImagesRequest);
				amiAvailabe = descImgResult.getImages().size() > 0;
			}

			// create new instance from own ami
			logger.info(
					"[{}]【Start Instance】creat new instance from previous backed up ami [{}] for [{}]",
					user.getName(), amiId, user.getName());
			RunInstancesRequest runInstancesRequest = new RunInstancesRequest();
			runInstancesRequest
					.withImageId(user.getAmiIds().get(index))
					// with user's own ami
					.withBlockDeviceMappings(blockDeviceMapping)
					// with the backup snapshot volumne
					.withInstanceType(EC2_INSTANCE_TYPE).withMinCount(1)
					.withMaxCount(1).withKeyName(user.getKeyName())
					.withSecurityGroups(user.getSecurityGroupName());

			RunInstancesResult runInstancesResult = amazonEC2Client
					.runInstances(runInstancesRequest);

			// wait instance to change its state to running
			logger.info(
					"[{}]【Wait for instance】wait instance to be in running state for [{}]",
					user.getName(), user.getName());
			boolean ready = hasRunningInstances(user);
			while (!ready) {
				System.out.print("*");
				Thread.sleep(COMM_RETRY_WAITING_TIME);
				ready = hasRunningInstances(user);
			}
			// get instance ids
			String reservationID = runInstancesResult.getReservation()
					.getReservationId();
			logger.debug("reservation id:{}", reservationID);

			logger.debug("getting instance ids...");
			List<Instance> instanceIds = getInstanceIdsByRerservationID(reservationID);
			while (instanceIds.size() == 0) {
				instanceIds = getInstanceIdsByRerservationID(reservationID);
			}
			// reassociate elastic ip
			String nextInstanceId = instanceIds.get(index).getInstanceId();
			String nextAvailableElasticIp = user.getElasticIP().get(index);
			logger.debug(
					"associate instance id[{}] with elastic ip [{}] for [{}]",
					nextInstanceId, nextAvailableElasticIp, user.getName());
			AssociateAddressRequest associateAddressRequest = new AssociateAddressRequest();
			associateAddressRequest.withInstanceId(nextInstanceId)
					.withPublicIp(nextAvailableElasticIp);
			amazonEC2Client.associateAddress(associateAddressRequest);
			// add instance id
			logger.debug("add instance id [{}] for [{}]", nextInstanceId,
					user.getName());
			user.addInstanceId(nextInstanceId);

			index++;
		}

	}

	private static int checkInstancesWithSpecificStatus(User user,
			String... statuses) throws InterruptedException {
		logger.debug("check Instances with {} status for [{}]", statuses,
				user.getName());
		DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
		Filter stateFilter = new Filter();
		stateFilter.setName("instance-state-name");
		Collection<String> values = Arrays.asList(statuses);
		stateFilter.setValues(values);
		Filter groupNameFilter = new Filter();
		groupNameFilter.setName("instance.group-name");
		groupNameFilter.setValues(Arrays.asList(user.getSecurityGroupName()));
		describeInstancesRequest.withFilters(stateFilter).withFilters(
				stateFilter, groupNameFilter);
		DescribeInstancesResult descInstanceResult = amazonEC2Client
				.describeInstances(describeInstancesRequest);

		int instanceAmount = 0;

		List<Reservation> reservations = descInstanceResult.getReservations();
		for (Reservation reservation : reservations) {
			instanceAmount += reservation.getInstances().size();
		}

		return instanceAmount;
	}


	public static void resumeMonitoring(User user) {
		logger.debug("resume cloud watch alarms in {} for [{}]",
				user.getAutoScalingGroupName(), user.getName());
		EnableAlarmActionsRequest enableAlarmActionsRequest = new EnableAlarmActionsRequest();
		enableAlarmActionsRequest.withAlarmNames(
				user.getPutMetricScaleUpAlarmName(),
				user.getPutMetricScaleDownAlarmName());
		amazonCloudWatchClient.enableAlarmActions(enableAlarmActionsRequest);
	}

	private static void suspendMonitoring(User user) {
		logger.debug("suspend cloud watch alarms in {} for [{}]",
				user.getAutoScalingGroupName(), user.getName());
		DisableAlarmActionsRequest disableAlarmActionsRequest = new DisableAlarmActionsRequest();
		disableAlarmActionsRequest.withAlarmNames(
				user.getPutMetricScaleUpAlarmName(),
				user.getPutMetricScaleDownAlarmName());
		amazonCloudWatchClient.disableAlarmActions(disableAlarmActionsRequest);
	}

	public static void resumeAutoScaling(User user) {

		DescribeAutoScalingGroupsRequest describeAutoScalingGroupsRequest = new DescribeAutoScalingGroupsRequest();
		describeAutoScalingGroupsRequest.withAutoScalingGroupNames(user
				.getAutoScalingGroupName());
		DescribeAutoScalingGroupsResult describeAutoScalingGroupResult = amazonAutoScalingClient
				.describeAutoScalingGroups(describeAutoScalingGroupsRequest);
		List<AutoScalingGroup> groups = describeAutoScalingGroupResult
				.getAutoScalingGroups();
		if (groups.size() == 0)
			return;

		logger.debug("resume auto scaling process in {} for [{}]",
				user.getAutoScalingGroupName(), user.getName());
		ResumeProcessesRequest resumeProcessesRequest = new ResumeProcessesRequest();
		resumeProcessesRequest.withAutoScalingGroupName(user
				.getAutoScalingGroupName());
		amazonAutoScalingClient.resumeProcesses(resumeProcessesRequest);
	}

	private static void suspendAutoScaling(User user) {
		DescribeAutoScalingGroupsRequest describeAutoScalingGroupsRequest = new DescribeAutoScalingGroupsRequest();
		describeAutoScalingGroupsRequest.withAutoScalingGroupNames(user
				.getAutoScalingGroupName());
		DescribeAutoScalingGroupsResult describeAutoScalingGroupResult = amazonAutoScalingClient
				.describeAutoScalingGroups(describeAutoScalingGroupsRequest);
		List<AutoScalingGroup> groups = describeAutoScalingGroupResult
				.getAutoScalingGroups();
		if (groups.size() == 0)
			return;

		logger.debug("suspend auto scaling process in {} for [{}]",
				user.getAutoScalingGroupName(), user.getName());
		SuspendProcessesRequest suspendProcessesRequest = new SuspendProcessesRequest();
		suspendProcessesRequest.withAutoScalingGroupName(user
				.getAutoScalingGroupName());
		amazonAutoScalingClient.suspendProcesses(suspendProcessesRequest);
	}

	private static void createSnapshotAndAmiBackup(User user) throws Exception {
		// ensure all the instances of this user have been stopped
		boolean allStopped = false;
		while (allStopped == false) {
			Thread.sleep(COMM_RETRY_WAITING_TIME);// wait for
																	// 10s
			allStopped = checkInstancesWithSpecificStatus(user, "stopped","terminated") >= user.getInstanceIds().size();
			logger.debug("Have all instances of [{}] been stopped?-----{} ",
					user.getName(), allStopped);
		}

		// Snapshot the EBS volumn (the snapshot will be saved in s3,which is
		// cheaper and reliable)
		// for more info see
		// here:http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html
		List<String> instanceIds = getAllInstanceIdsWithStatusFilterForUser(user, "stopped");

		DescribeVolumesResult descVolResult = amazonEC2Client.describeVolumes();
		String volumeId = null;
		for (Volume vol : descVolResult.getVolumes()) {
			List<VolumeAttachment> attachments = vol.getAttachments();
			if (attachments.size() <= 0)
				continue;
			VolumeAttachment attachment = attachments.get(0);
			if (instanceIds.contains(attachment.getInstanceId())) {
				volumeId = vol.getVolumeId();

				logger.info(
						"[{}]【Create Snapshot】creating snapshot from volume[{}] for [{}]",
						user.getName(), volumeId, user.getName());
				CreateSnapshotRequest createSnapshotRequest = new CreateSnapshotRequest();
				createSnapshotRequest.withVolumeId(volumeId).withDescription(
						"[" + new Date().toString() + "]snapshot for "
								+ user.getName());

				CreateSnapshotResult snapshotResult = amazonEC2Client
						.createSnapshot(createSnapshotRequest);
				Snapshot snapshot = snapshotResult.getSnapshot();
				user.addSnapshotId(snapshot.getSnapshotId());
			}
		}

		// Create a Amazon EBS-Backed AMI
		// for more info see
		// here:http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ComponentsAMIs.html#storage-for-the-root-device
		for (String instanceId : instanceIds) {
			String amiName = "ami-" + user.getName() + "-" + UUID.randomUUID();

			logger.info(
					"[{}]【Create AMI】creating AMI of [{}] from instance [{}]",
					user.getName(), instanceId, user.getName());

			CreateImageRequest createImageRequest = new CreateImageRequest();
			createImageRequest
					.withInstanceId(instanceId)
					.withName(amiName)
					.withDescription(
							"[" + new Date().toString()
									+ "]suspended vm ami for " + user.getName());

			CreateImageResult createImageResult = amazonEC2Client
					.createImage(createImageRequest);
			user.addAmiId(createImageResult.getImageId());
		}
	}

	private static void terminateInstances(User user) throws Exception {

		// stopInstance & create snapshot&ami
		stopInstances(user);

		createSnapshotAndAmiBackup(user);

		logger.info(
				"[{}]【Terminate Instance】trying to terminate all the instances of [{}]",
				user.getName(), user.getName());
		// send terminate instance request to aws
		TerminateInstancesRequest terminateInstancesRequest = new TerminateInstancesRequest();
		terminateInstancesRequest.withInstanceIds(user.getInstanceIds());
		amazonEC2Client.terminateInstances(terminateInstancesRequest);

		// modify internal state
		user.getInstanceIds().clear();

	}

	/**
	 * 
	 * @param user
	 * @throws Exception
	 */
	public static void stopInstances(User user) throws Exception {

		//suspend CPU Utilization monitoring thread
//		cwCheckerThread.interrupt();
		// stop auto scaling & cloudWatch alrams
		suspendAutoScaling(user);
		suspendMonitoring(user);

		if (!hasRunningInstances(user))
			return;

		// stop the instance
		logger.info(
				"[{}]【Stop Instance】trying to stop all the instances of [{}]",
				user.getName(), user.getName());
		StopInstancesRequest stopInstancesRequest = new StopInstancesRequest();
		stopInstancesRequest.withInstanceIds(getAllInstanceIdsWithStatusFilterForUser(user,"running"));
		amazonEC2Client.stopInstances(stopInstancesRequest);

	}

}
